package at.codejedi.sto.model.combat;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.GenericGenerator;

@Entity
public class PlayerBattle implements IPlayerBattle {

	@Entity
	@Inheritance(strategy = InheritanceType.JOINED)
	@Table(name = "PlayerBattle_Damage")
	public abstract class Damage implements IDamage {

		private double allDamage = 0;

		private double averageDamage;

		private double damagePerSecond = 0;

		private List<Entry> damages;

		private double maxDamage;

		private int secondsPlayed;

		private String uuid;

		public Damage() {
		}

		public Damage(List<Entry> damages, int secondsPlayed) {
			this.damages = damages;
			this.secondsPlayed = secondsPlayed;
		}

		@Override
		public double getAllDamage() {
			if (allDamage == 0)
				allDamage = loadAllDamage(getDamages(), true);
			return allDamage;
		}

		@Override
		public double getAverageDamage() {
			if (averageDamage == 0)
				averageDamage = loadAverageDamage(getDamages(),
						getSecondsPlayed());
			return averageDamage;
		}

		@Override
		public double getDamagePerSecond() {
			if (damagePerSecond == 0)
				damagePerSecond = loadDamagePerSecond(getAllDamage(),
						getSecondsPlayed());
			return damagePerSecond;
		}

		@OneToMany(targetEntity = Entry.class)
		@Cascade(CascadeType.ALL)
		@JoinColumn(name = "damages")
		public List<Entry> getDamages() {
			return damages;
		}

		@Override
		public double getMaxDamage() {
			if (maxDamage == 0)
				maxDamage = loadMaxDamage(getDamages());
			return maxDamage;
		}

		private int getSecondsPlayed() {
			return secondsPlayed;
		}

		@Id
		@GeneratedValue(generator = "system-uuid")
		@GenericGenerator(name = "system-uuid", strategy = "uuid")
		@Column(name = "uuid", unique = true)
		public String getUuid() {
			return uuid;
		}

		private double loadAllDamage(List<Entry> damageDealt,
				boolean noShieldDamage) {
			double allNetDamage = 0;
			for (Entry entry : damageDealt) {
				if (entry.getDamageType().equals("Shield") && noShieldDamage)
					continue;

				double netDamage = loadDamage(entry);
				netDamage = Math.abs(netDamage);
				allNetDamage += netDamage;
			}
			return allNetDamage;
		}

		private double loadAverageDamage(List<Entry> damageDealt,
				int secondsPlayed) {
			double all = 0;
			for (Entry e : damageDealt) {
				all += Math.abs(loadDamage(e));
			}
			return all / secondsPlayed;
		}

		public abstract double loadDamage(Entry e);

		private double loadDamagePerSecond(double allNetDamageDealt,
				int secondsPlayed) {
			return allNetDamageDealt / secondsPlayed;
		}

		private double loadMaxDamage(List<Entry> damageDealt) {
			double max = 0;
			for (Entry entry : damageDealt) {
				double damage = Math.abs(loadDamage(entry));
				if (damage > max)
					max = damage;
			}
			return max;
		}

		@Override
		public void preLoad() {
			// TODO Auto-generated method stub

		}

		public void setAllDamage(double allDamage) {
			this.allDamage = allDamage;
		}

		public void setAverageDamage(double averageDamage) {
			this.averageDamage = averageDamage;
		}

		public void setDamagePerSecond(double damagePerSecond) {
			this.damagePerSecond = damagePerSecond;
		}

		public void setDamages(List<Entry> damages) {
			this.damages = damages;
		}

		public void setMaxDamage(double maxDamage) {
			this.maxDamage = maxDamage;
		}

		public void setSecondsPlayed(int secondsPlayed) {
			this.secondsPlayed = secondsPlayed;
		}

		public void setUuid(String uuid) {
			this.uuid = uuid;
		}

	}

	@Entity
	@Inheritance(strategy = InheritanceType.JOINED)
	@Table(name = "PlayerBattle_NetDamage")
	public class NetDamage extends Damage {

		public NetDamage() {
		}

		public NetDamage(List<Entry> damages, int secondsPlayed) {
			super(damages, secondsPlayed);
			// TODO Auto-generated constructor stub
		}

		@Override
		public double loadDamage(Entry e) {
			return e.getNetDamage();
		}

	}

	@Entity
	@Inheritance(strategy = InheritanceType.JOINED)
	@Table(name = "PlayerBattle_RawDamage")
	public class RawDamage extends Damage {

		public RawDamage() {
		}

		public RawDamage(List<Entry> damages, int secondsPlayed) {
			super(damages, secondsPlayed);
		}

		@Override
		public double loadDamage(Entry e) {
			return e.getRawDamage();
		}

	}

	private static List<Entry> LoadDamageDealt(List<Entry> entries) {
		List<Entry> damageDealt = new ArrayList<Entry>();
		for (Entry entry : entries) {
			if (entry.getFromType().startsWith("P"))
				damageDealt.add(entry);
		}
		return damageDealt;
	}

	private static List<Entry> loadDamageReceived(List<Entry> entries) {
		List<Entry> damageReceived = new ArrayList<Entry>();
		for (Entry entry : entries) {
			if (entry.getToType().startsWith("P"))
				damageReceived.add(entry);
		}
		return damageReceived;
	}

	private long beginningMillis;
	List<Entry> damageDealt;

	List<Entry> damageReceived;

	private long endingMillis;

	private List<Entry> entries;

	private double logEntriesPerSecond;

	private IDamage netDamageDealt;

	private IDamage netDamageReceived;

	private IPlayer player;

	private IDamage rawDamageDealt;

	private IDamage rawDamageReceived;

	private int secondsPlayed;

	private String uuid;

	/*
	 * (non-Javadoc)
	 * 
	 * @see at.frima.sto.parser.statistics.IBattleStatistic#getBeginningMillis()
	 */
	@Override
	public long getBeginningMillis() {
		return beginningMillis;
	}

	@OneToMany(targetEntity = Entry.class)
	@JoinColumn(name = "damageDealt")
	@Cascade(CascadeType.ALL)
	public List<Entry> getDamageDealt() {
		if (damageDealt == null)
			damageDealt = LoadDamageDealt(entries);
		return damageDealt;
	}

	@OneToMany(targetEntity = Entry.class)
	@JoinColumn(name = "damageReceived")
	@Cascade(CascadeType.ALL)
	public List<Entry> getDamageReceived() {
		if (damageReceived == null)
			damageReceived = loadDamageReceived(entries);
		return damageReceived;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see at.frima.sto.parser.statistics.IBattleStatistic#getEndingMillis()
	 */
	@Override
	public long getEndingMillis() {
		return endingMillis;
	}

	@Override
	@OneToMany(targetEntity = Entry.class, fetch = FetchType.EAGER)
	@Cascade(CascadeType.ALL)
	@JoinColumn(name = "entries")
	public List<Entry> getEntries() {
		return entries;
	}

	@Override
	public double getLogEntriesPerSecond() {
		if (logEntriesPerSecond == 0)
			logEntriesPerSecond = loadLogEntriesPerSecond(getEntries(),
					getSecondsPlayed());
		return logEntriesPerSecond;
	}

	@Override
	@OneToOne(targetEntity = NetDamage.class)
	@Cascade(CascadeType.ALL)
	public IDamage getNetDamageDealt() {
		if (netDamageDealt == null)
			netDamageDealt = new NetDamage(getDamageDealt(), getSecondsPlayed());
		return netDamageDealt;
	}

	@Override
	@OneToOne(targetEntity = NetDamage.class)
	@Cascade(CascadeType.ALL)
	public IDamage getNetDamageReceived() {
		if (netDamageReceived == null)
			netDamageReceived = new NetDamage(getDamageReceived(),
					getSecondsPlayed());
		return netDamageReceived;
	}

	@Override
	@ManyToOne(targetEntity = Player.class)
	@Cascade(CascadeType.ALL)
	public IPlayer getPlayer() {
		return player;
	}

	@Override
	@OneToOne(targetEntity = RawDamage.class)
	@Cascade(CascadeType.ALL)
	public IDamage getRawDamageDealt() {
		if (rawDamageDealt == null)
			rawDamageDealt = new RawDamage(getDamageDealt(), getSecondsPlayed());
		return rawDamageDealt;
	}

	@Override
	@OneToOne(targetEntity = RawDamage.class)
	@Cascade(CascadeType.ALL)
	public IDamage getRawDamageReceived() {
		if (rawDamageReceived == null)
			rawDamageReceived = new RawDamage(getDamageReceived(),
					getSecondsPlayed());
		return rawDamageReceived;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see at.frima.sto.parser.statistics.IBattleStatistic#getSecondsPlayed()
	 */
	@Override
	public int getSecondsPlayed() {
		if (secondsPlayed == 0)
			secondsPlayed = loadSecondsPlayed();
		return secondsPlayed;
	}

	@Id
	@GeneratedValue(generator = "system-uuid")
	@GenericGenerator(name = "system-uuid", strategy = "uuid")
	@Column(name = "uuid", unique = true)
	public String getUuid() {
		return uuid;
	}

	private double loadLogEntriesPerSecond(List<Entry> entries2,
			int secondsPlayed) {
		return entries.size() / secondsPlayed;
	}

	private int loadSecondsPlayed() {
		long diff = endingMillis - beginningMillis;
		return (int) diff / 1000;
	}

	public void preload() {
		damageDealt = LoadDamageDealt(entries);
		damageReceived = loadDamageReceived(entries);

		secondsPlayed = loadSecondsPlayed();

	}

	public void setBeginningMillis(long beginningMillis) {
		this.beginningMillis = beginningMillis;
	}

	public void setDamageDealt(List<Entry> damageDealt) {
		this.damageDealt = damageDealt;
	}

	public void setDamageReceived(List<Entry> damageReceived) {
		this.damageReceived = damageReceived;
	}

	public void setEndingMillis(long endingMillis) {
		this.endingMillis = endingMillis;
	}

	public void setEntries(List<Entry> entries) {
		this.entries = entries;
	}

	public void setLogEntriesPerSecond(double logEntriesPerSecond) {
		this.logEntriesPerSecond = logEntriesPerSecond;
	}

	public void setNetDamageDealt(IDamage netDamageDealt) {
		this.netDamageDealt = netDamageDealt;
	}

	public void setNetDamageReceived(IDamage netDamageReceived) {
		this.netDamageReceived = netDamageReceived;
	}

	public void setPlayer(IPlayer player) {
		this.player = player;
	}

	public void setRawDamageDealt(IDamage rawDamageDealt) {
		this.rawDamageDealt = rawDamageDealt;
	}

	public void setRawDamageReceived(IDamage rawDamageReceived) {
		this.rawDamageReceived = rawDamageReceived;
	}

	public void setSecondsPlayed(int secondsPlayed) {
		this.secondsPlayed = secondsPlayed;
	}

	public void setUuid(String uuid) {
		this.uuid = uuid;
	}
}
